<!--输入框-->
<!-- 自动提示论坛代码、表情包、第三方表情包-->
<script lang="ts" setup>
import {ref, watch} from 'vue'
import {Codemirror} from "vue-codemirror";
import {EditorView} from "@codemirror/view";
import {EditorSelection, EditorState} from "@codemirror/state";
import {autocompletion, type Completion, CompletionContext, type CompletionResult} from '@codemirror/autocomplete'
import {ElMessageBox} from "element-plus";

const props = defineProps({
  modelValue: {type: String},
  autoCompletion: {type: Array<CompletionOption>}
})


export interface CompletionOption {
  expr: RegExp
  options: Array<Completion>
}

// 正文
const content = ref<string>(props.modelValue || "");

const bbsCodeApply = (prefix: string, suffix: string) => (view: EditorView, completion: Completion, from: number, to: number) => {
  const text = view.state.doc.toString()
  const insert = prefix + suffix + (suffix && text[to] !== ']' ? ']' : '');
  const anchor = from + prefix.length;
  // 插入文字并选中占位文本
  view.dispatch({changes: {from, to, insert}, selection: EditorSelection.range(anchor, anchor)})
}

const completionOptions: CompletionOption[] = [
  {
    expr: /\[[\w=]*/,
    options: [
      {label: "[b]", detail: "粗体", apply: bbsCodeApply("[b]", "[/b")},
      {label: "[del]", detail: "删除线", apply: bbsCodeApply("[del]", "[/del")},
      {label: "[size]", detail: "字体大小", apply: bbsCodeApply("[size=100%]", "[/size")},
      {label: "[quote]", detail: "引用框", apply: bbsCodeApply("[quote]", "[/quote")},
      {label: "[h]", detail: "段落标题", apply: bbsCodeApply("[h]", "[/h")},
      {label: "[u]", detail: "下划线", apply: bbsCodeApply("[u]", "[/u")},
      {label: "[i]", detail: "斜体", apply: bbsCodeApply("[i]", "[/i")},
      {label: "[code]", detail: "代码框", apply: bbsCodeApply("[code]", "[/code")},
      {label: "[uid]", detail: "用户ID", apply: bbsCodeApply("[uid]", "[/uid")},
      {label: "[tid]", detail: "主题ID", apply: bbsCodeApply("[tid]", "[/tid")},
      {label: "[pid]", detail: "回复ID", apply: bbsCodeApply("[pid]", "[/pid")},
      {label: "[collapse]", detail: "折叠框", apply: bbsCodeApply("[collapse=标题可选]", "[/collapse")},
      {label: "[url]", detail: "链接", apply: bbsCodeApply("[url=标题可选]", "[/url")},
      {label: "[list]", detail: "删除线", apply: bbsCodeApply("[list]\n[*]列表项\n[*]列表项\n[*]列表项", "\n[/list")},
      {label: "[table]", detail: "表格", apply: bbsCodeApply("[table]\n[tr][td]单元格1[td][td]单元格2[td][/tr]", "\n[/table")},
      {label: "[tr]", detail: "表格行", apply: bbsCodeApply("[tr]", "[/tr")},
      {label: "[td]", detail: "表格单元格", apply: bbsCodeApply("[td]", "[/td")},
      {label: "[dice]", detail: "骰子", apply: bbsCodeApply("[dice]1d100", "[/dice")},
    ]
  },

]

const autoCompletionMatcher = (context: CompletionContext): CompletionResult | null | Promise<CompletionResult | null> => {
  const options: Completion[] = []
  let from = 0
  for (let it of completionOptions) {
    const word = context.matchBefore(it.expr);
    if (word) {
      from = word.from || 0
      options.push(...it.options)
    }
  }
  if (props.autoCompletion) {
    for (let it of props.autoCompletion) {
      const word = context.matchBefore(it.expr);
      if (word) {
        from = word.from || 0
        options.push(...it.options)
      }
    }
  }
  // console.log({from, options})
  return {from, options}
}


let editorView: EditorView
let editorState: EditorState
const extensions = [
  EditorView.lineWrapping,
  autocompletion({override: [autoCompletionMatcher]})
  //   内容变化监控
  // EditorView.updateListener.of(({state}) => {
  // console.log(state.doc.toString())
  // }),
]
const onReady = (payload: any): any => {
  editorView = payload.view as EditorView
  editorState = payload.state as EditorState
  editorView.dispatch({selection: {anchor: editorState.doc.length}})
}


const bracket = (prefix = "", suffix = "") => {
  const {from, to} = editorView.state.selection.ranges[0]
  // 插入后缀
  editorView.dispatch({
    changes: {from: to, to, insert: suffix}
  })
  // 插入前缀 选中中间
  editorView.dispatch({
    changes: {from, to: from, insert: prefix},
    selection: EditorSelection.range(from + prefix.length, to + prefix.length)
  })
  focus()
}

const onPaste = (event: ClipboardEvent) => {
  const clipData = event.clipboardData
  if (!clipData) return
  // console.log('clipData', clipData)
  const files = [...clipData.files]
  // 原始文本
  const text = clipData.getData('text/plain')
  // html
  const html = clipData.getData('text/html')
  // 如果html中包含表格代码，将其解析为 bbs code
  const table = parseTable(html)
  if (table) {
    const {to} = editorView.state.selection.ranges[0]
    ElMessageBox.confirm("检测到表格数据，你希望的处理是？（点 x 或 esc不做处理）", "表格数据", {
      confirmButtonText: "粘贴代码",
      cancelButtonText: "上传截图",
      distinguishCancelAndClose: true,
    }).then(() => {
      editorView.dispatch({
        changes: {from: to - text.replace(/\n/g, '').length, to, insert: table}
      })
    }).catch(reason => {
      if (reason === 'cancel') {
        // 清空原始文本
        editorView.dispatch({
          changes: {from: to - text.replace(/\n/g, '').length, to}
        })
        // 抛出事件，上传文件列表
        emit('pastedFile', files)
      }
    })
  } else if (files.length > 0) {
    emit('pastedFile', files)
  }

  console.log('粘贴', {files, text, html, table})
}

const parseTable = (html: string): string | undefined => {
  const doc = new DOMParser().parseFromString(html, 'text/html')
  const tables = [...doc.getElementsByTagName("table")]
  if (tables.length === 0) return undefined

  return tables.map(table => {
    const rows = [...table.getElementsByTagName("tr")]
    const rowCode = rows.map(td => {
      const tds = [...td.getElementsByTagName("td")]
      return `[tr]${tds.map(parseTd).join('')}[/tr]`
    }).join('\n')
    return `[table]\n${rowCode}\n[/table]`
  }).join('\n')
}

const parseTd = (td: HTMLTableCellElement): string => {
  const colspan = td.getAttribute("colspan");
  const rowspan = td.getAttribute("rowspan");
  const spanArr = []
  if (colspan) spanArr.push(`colspan=${colspan}`)
  if (rowspan) spanArr.push(`rowspan=${rowspan}`)
  const attr = spanArr.length > 0 ? ' ' + spanArr.join(' ') : ''
  return `[td${attr}]${td.innerText}[/td]`
}


const emit = defineEmits(['update:modelValue', 'pastedFile', 'sendPost'])

const replace = (searchValue: string | RegExp, replacement: string = "") => content.value = content.value.replace(searchValue, replacement)

const focus = () => setTimeout(() => editorView.focus(), 50)

const readme = () => {
  const lines: string[] = [
    "输入辅助：",
    "1、输入中括号会提示论坛代码",
    "2、输入'@'会提示官方表情包",
    "3、输入'#'会提示第三方表情包",
    "粘贴操作：",
    "1、粘贴从Excel复制的表格会提示选择：解析为论坛代码、上传图片、保留纯文本",
    "2、粘贴图片或文件会作为附件上传",
  ]

  ElMessageBox.alert(lines.join('\n'), '操作说明', {
    customClass: 'alert-message',
    closeOnClickModal: true,
    closeOnPressEscape: true
  })
}

watch(content, (c: string) => emit('update:modelValue', c))

defineExpose({
  bracket, bbsCodeApply, replace, focus
})
</script>

<template>
  <div>
    <codemirror ref="editor"
                v-model="content"
                :extensions="extensions"
                :placeholder="'请输入正文内容, 字数过少时会自动添加字数补丁\n\n\n'"
                autofocus
                style="text-align: start"
                @paste="onPaste"
                @ready="onReady"
    />
    <div id="post-text-area-button">
      <el-button
          v-shortkey="['ctrl','enter']" type="success"
          @click="emit('sendPost',content)" @shortkey="emit('sendPost',content)">提交 (Ctrl+Enter)
      </el-button>
      <el-button type="primary" @click="readme">操作说明</el-button>
    </div>
  </div>
</template>

<style scoped>
#post-text-area-button {
  margin-top: 10px;
}
</style>